﻿/*
Discio
Copyright (c) 2013 - Walter M. Soto Reyes
Microsoft Public License (Ms-PL)
License Agreement:  https://discio.codeplex.com/license
*/
using System;
using System.Collections.Generic;
using System.Linq; 
using System.IO; 
using System.Web.Script.Serialization;

namespace Discio
{
    public class Manager<T>
    { 
        private string _master = "";
        private Source src = new Source();

        /// <summary>
        /// Data manager
        /// </summary>
        /// <param name="name">Master</param>
        public Manager(string name) : 
            this(name, "") { }


        /// <summary>
        /// Data manager
        /// </summary>
        /// <param name="name">Master</param>
        /// <param name="source">Storage source</param>
        public Manager(string name, Source source)
        {
            if (source == null)
            {
                source = SiteSources.Source.Default;
            }
            
            src = source ?? new Source();

            toInsert = new List<T>();
            toUpdate = new List<T>();
            toDelete = new List<T>();
 
            if (src.IsValid(name))
            {
                Storage = src.StoragePath(name);
                _master = src.MasterPath(name); 
            }
            else
            {
                throw new DiscioException("Invalid DataFolder in source");
            }
  
        }

        /// <summary>
        /// Data manager
        /// </summary>
        /// <param name="name">Master</param>
        /// <param name="sourceKey">Storage source key</param>
        public Manager(string name, string sourceKey)
        {
            Source source = SiteSources.Source.Default;

            if (!string.IsNullOrEmpty(sourceKey))
            {
                source = SiteSources.Source[sourceKey];
            }

            src = source ?? new Source();

            toInsert = new List<T>();
            toUpdate = new List<T>();
            toDelete = new List<T>();

            if (src.IsValid(name))
            {
                Storage = src.StoragePath(name);
                _master = src.MasterPath(name);
            }
            else
            {
                throw new DiscioException("Invalid DataFolder in source");
            }

        }

        public void Commit()
        { 
            InsertItems();
            UpdateItems();
            DeleteItems();
        }

        private string Serialize(object o)
        {
            var serializer = new JavaScriptSerializer {MaxJsonLength = src.MaxJsonLength};
            return serializer.Serialize(o);
        }

       

        #region CRUD

        private void CreateGUIDs()
        {
            foreach (T item in toInsert)
            {
                ((IDiscio)item).ID = Guid.NewGuid().ToString();
            }
        }

        private void InsertItems()
        {
            if (toInsert != null && toInsert.Count > 0)
            {
                CreateGUIDs();

                var l = Select();

                l.AddRange(toInsert);


                ToStorage(Serialize(l));
                toInsert.Clear();

            }
        }

        private void UpdateItems()
        {
            if (toUpdate != null && toUpdate.Count > 0)
            {
                var l = Select();
               
                foreach (var item in toUpdate)
                {
                    int index = l.FindIndex(m => ((IDiscio)m).ID == ((IDiscio)item).ID ); 
                    if (index != -1)
                    {
                        l[index] = item;
                    }
                }

                ToStorage(Serialize(l));
                toUpdate.Clear();
            }
        }

        private void DeleteItems()
        {
            if (toDelete != null && toDelete.Count > 0)
            {
                var l = Select();

                foreach (var item in toDelete)
                {
                   l.RemoveAll(m => ((IDiscio)m).ID == ((IDiscio)item).ID); 
                }

                ToStorage(Serialize(l));
                toDelete.Clear();
            }
        }

        #endregion


        #region "Queries"

        private string LoadJson()
        { 
            return File.ReadAllText(_master);
        }

        private List<T> LoadJsonObjects()
        {
            var l = new List<T>();

            string json = LoadJson();
            if (json.Length > 0)
            {
                var serializer = new JavaScriptSerializer {MaxJsonLength = src.MaxJsonLength};
                l = serializer.Deserialize<List<T>>(json); 
            }

            return l;
        }

        /// <summary>
        /// Is any record matching the criteria exists
        /// </summary>
        /// <param name="where"></param>
        /// <returns></returns>
        public bool Any(Func<T, bool> where)
        {
            var l = LoadJsonObjects();

            if (where != null)
            {
                return l.Any(where);
            }

            return false;
            
        }

        /// <summary>
        /// Select list of records using a "where" predicate
        /// </summary>
        /// <param name="where">predicate</param>
        /// <returns>List</returns>
        public List<T> Select(Func<T, bool> where)
        {
            var l = LoadJsonObjects();

            if (where != null)
            { 
                return l.Where(where).ToList();
            }

            return l;
        }

        /// <summary>
        /// Select all records in the dataset
        /// </summary>
        /// <returns>List</returns>
        public List<T> Select()
        {
            return Select(null);
        }

        /// <summary>
        /// Select a page from a set with default size or 5
        /// </summary>
        /// <param name="page">Page number</param>
        /// <returns>List</returns>
        public List<T> Page(int page) {
            return Page(page, 5);
        }

        /// <summary>
        /// Select a page from a dataset
        /// </summary>
        /// <param name="page">Page number</param>
        /// <param name="size">Page size</param>
        /// <returns>List</returns>
        public List<T> Page(int page, int size)
        {
            var l = Select();
            return l.Skip((page - 1)*size).Take(size).ToList();
        }


        /// <summary>
        /// Select a page from a dataset
        /// </summary>
        /// <param name="page">Page number</param>
        /// <param name="size">Page size</param>
        /// <param name="where">Predicate</param>
        /// <returns>List</returns>
        public List<T> Page(int page, int size, Func<T, bool> where)
        {
            var l = Select(where);
            return l.Skip((page - 1) * size).Take(size).ToList();
        }

        /// <summary>
        /// Return number of pages by size
        /// </summary>
        /// <param name="size">Size</param>
        /// <returns>count</returns>
        public int PageCount(int size)
        {
            double count = Count();
            return (int)Math.Ceiling((count / size));
        }

        /// <summary>
        /// Return number of pages by size
        /// </summary>
        /// <param name="size">Size</param>
        /// <param name="where">Predicate</param>
        /// <returns>count</returns>
        public int PageCount(int size, Func<T, bool> where)
        {
            double count = Count(where);
            return (int)Math.Ceiling((count / size));
        }


        /// <summary>
        /// Load record by id
        /// </summary>
        /// <param name="id">Record id</param>
        /// <returns>Record</returns>
        public T Load(string id)
        {
            var l = Select();
            return l.FirstOrDefault(m => ((IDiscio)m).ID == id);
        }

        /// <summary>
        /// Number of records in dataset
        /// </summary>
        /// <returns>Int</returns>
        public int Count()
        {
            return Count(null);
        }

        /// <summary>
        /// Number of records that match predicate
        /// </summary>
        /// <param name="where">Predicate</param>
        /// <returns>int</returns>
        public int Count(Func<T, bool> where)
        {
            var l = Select(where);

            return l.Count;
        }

        public void RemoveAll()
        {
            ToStorage("[]");
        }
 

        #endregion


        /// <summary>
        /// Insert an item
        /// </summary>
        /// <param name="item">IDiscio item</param>
        public void Insert(T item)
        {
            if (item != null)
            {
                toInsert.Add(item.EncryptHelper());
            }
            
        }

        /// <summary>
        /// Insert a list of items
        /// </summary>
        /// <param name="items">IDiscio items</param>
        public void Insert(List<T> items)
        {
            if (items != null && items.Count > 0)
            {
                toInsert.AddRange(items.EncryptHelper());
            } 
        }

        /// <summary>
        /// Add an item to be deleted
        /// </summary>
        /// <param name="item">IDiscio item</param>
        public void Delete(T item)
        {
            if (item != null)
            {
                toDelete.Add(item);
            } 
        }

        /// <summary>
        /// Add items to be deleted
        /// </summary>
        /// <param name="items">IDiscio items</param>
        public void Delete(List<T> items)
        {
            if (items != null && items.Count > 0)
            {
                toDelete.AddRange(items);
            } 
        }

        /// <summary>
        /// Add an item to be updated
        /// </summary>
        /// <param name="item">IDiscio item</param>
        public void Update(T item)
        {
            if (item != null)
            {
                toUpdate.Add(item.EncryptHelper()); 
            } 
        }

        /// <summary>
        /// Add items to be updated
        /// </summary>
        /// <param name="items">IDiscio items</param>
        public void Update(List<T> items)
        {
            if (items != null && items.Count > 0)
            {
                toUpdate.AddRange(items.EncryptHelper());
            }

        }
       

        private void ToStorage(string json)
        {
            if (!string.IsNullOrEmpty(json))
            {
                File.WriteAllText(_master, json);
            }
        }

        private List<T> toInsert;
        private List<T> toUpdate;
        private List<T> toDelete;


        public string Storage { set; get; }


    }
}
